/* 
 * Copyright 2009 Shared Object (www.sharedobject.ch)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package edos.prm.metrics;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Set;

import edos.prm.activity.Activity;
import edos.prm.activity.Operation;
import edos.prm.activity.ActivityManager.PrmActivityManager;
import edos.prm.artifact.Artifact;
import edos.prm.artifact.ArtifactManager;
import edos.prm.artifact.ArtifactSet;
import edos.prm.artifact.AttributePair;
import edos.prm.artifact.Directory;
import edos.prm.artifact.UnregisteredArtifactType;
import edos.prm.artifact.ArtifactManager.PrmArtifactManager;
import edos.prm.engine.PRM;
import edos.prm.engine.PrmSecurityManager;
import edos.prm.event.EventManager;
import edos.prm.metrics.Metric.OnDemandMetric;
import edos.prm.metrics.Metric.PrmOnDemandMetric;
import edos.prm.metrics.Metric.PrmOperationalMetric;
import edos.prm.project.Project;
import edos.prm.role.RoleException;
import edos.prm.util.Time;
import edos.prm.util.VersionNumber;

/**
 * Metrics Management interface allowing to register new Metrics, evaluate 
 * them, search for them and wait for a specific measure that a metric can
 * obtain.
 *
 * @version 1.0
 */
public interface MetricsManager extends Activity {
	/**
	 * Searches for Metrics matching a set of characteristics
	 * 
	 * @param attributes the characteristics to be matched by searched Metrics
	 * @return all Metrics matching specified characteristics
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public Set<Metric> find(AttributePair<?>...attributes);

	/**
	 * Retrieves all Metrics handled by this MetricsManager
	 * 
	 * @return all registered Metrics 
	 */
	public Set<Metric> getMetrics();

	/**
	 * Retrieves all Metrics handled by the MetricsManager for the specified
	 * project
	 * 
	 * @param project a registered PRM project
	 * @return all registered Metrics registered for a Project 
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public Set<Metric> getMetrics(Project project);

	/**
	 * Retrieves all Projects using a Metric
	 * 
	 * @param metric the shared Metric 
	 * @return all Projects using a Metric 
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public Set<Project> getProjects(Metric metric);

	/**
	 * Resisters a Metric to be handled by the MetricsManager for a Project
	 * 
	 * @param project the project for wich the Metric has to be registered
	 * @param expression the Metric to be handled by the MetricsManager
	 * @param name the String name of the metric
	 * @param description a Text description of the metric
	 * @param start the Time at which the metric becomes active
	 * @param end the time at which the metric becomes inoperational
	 * @param viewpoint the metric's viewpoint
	 * @return a metric that is registered with the PRM
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public Metric newMetric(Project project, MetricExpression expression,
			String name, String description, Time start, Time end,
			MetricViewpoint viewpoint) throws RoleException;

	/**
	 * Resisters a Metric to be handled by the MetricsManager for a Project
	 * 
	 * @param project the project for wich the Metric has to be registered
	 * @param operation the operation to which this metric is bound.
	 * @param expression the Metric to be handled by the MetricsManager
	 * @param name the String name of the metric
	 * @param description a Text description of the metric
	 * @param start the Time at which the metric becomes active
	 * @param end the time at which the metric becomes inoperational
	 * @param viewpoint the metric's viewpoint
	 * @return a metric that is registered with the PRM
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public Metric newOperationalMetric(Project project, Operation operation,
			MetricExpression expression, String name, String description,
			Time start, Time end, MetricViewpoint viewpoint);

	/**
	 * Evaluates the value of a Metric handled by the MetricsManager
	 * 
	 * @param metric the OnDemandMetric to be evaluated
	 * @return the value of the Metric
	 */
	public Number evaluate(Metric.OnDemandMetric metric);

	/**
	 * Raises a metric event for listening processes.
	 * 
	 * @param event an event that is signaled
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public void raise(MetricEvent event);

	/**
	 * Waits for a specific event to occur
	 * 
	 * @param event the event to be observed
	 * @throws RoleException if the caller is not permitted to execute this
	 * method.
	 */
	public void observe(MetricEvent event);

	/**
	 * Default Java implementation of the PRM's @{code MetricManager}.
	 */
	class PrmMetricsManager implements MetricsManager {

		private Time creationDate;
		private ArtifactManager am;
		private HashSet<Metric> metricSet;
		private HashMap<Project, HashSet<Metric>> metricToProjectMap;

		private static PrmMetricsManager instance;

		static {
			instance = new PrmMetricsManager();
		}

		public static PrmMetricsManager getInstance() {
			return instance;
		}

		private PrmMetricsManager() {
			this.creationDate = new Time();
			this.metricSet = new HashSet<Metric>();
			this.metricToProjectMap = new HashMap<Project, HashSet<Metric>>();
			PrmActivityManager.getInstance().register(this);
			this.am = PrmArtifactManager.getInstance();
			am.registerArtifactType(Metric.PrmOnDemandMetric.class);
			am.registerArtifactType(Metric.PrmOperationalMetric.class);
		}

		public Number evaluate(OnDemandMetric metric) {
			metric.getMetricExpression().evaluate();
			return metric.getMetricExpression().getMeasure();
		}

		public Set<Metric> find(AttributePair<?>... attributes) {
			PrmSecurityManager.roleCheck("find", getClass(), AttributePair[].class);
			HashSet<Metric> metrics = new HashSet<Metric>();
			try {
				Directory d = am.lookup(attributes);
				Set<String> properties = d.getContextList();
				for ( String property : properties ) {
					ArtifactSet<? extends Artifact> arts = d.select(property);
					for ( Artifact artifact : arts.getContents() )
						if ( artifact instanceof Metric )
							metrics.add((Metric)artifact);
				}
			} catch (Exception e) {
				e.printStackTrace();
			}
			return metrics;
		}

		public Set<Metric> getMetrics() {
			HashSet<Metric> result = new HashSet<Metric>();
			result.addAll(metricSet);
			return result;
		}

		public Set<Metric> getMetrics(Project project) {
			PrmSecurityManager.roleCheck("getMetrics", getClass(), Project.class);
			return this.metricToProjectMap.get(project);
		}

		public Set<Project> getProjects(Metric metric) {
			PrmSecurityManager.roleCheck("getProjects", getClass(), Metric.class);
			HashSet<Project> resultSet = new HashSet<Project>();
			for ( Project project : this.metricToProjectMap.keySet() ) {
				HashSet<Metric> map = this.metricToProjectMap.get(project);
				if ( map != null )
					if ( map.contains(metric) )
						resultSet.add(project);
			}
			return resultSet;
		}

		public Metric newMetric(Project project, MetricExpression expression,
				String name, String description, Time start, Time end,
				MetricViewpoint viewpoint) throws RoleException {
			PrmSecurityManager.roleCheck("newMetric", getClass(),
					new Class[] {Project.class,
				MetricExpression.class,
				String.class, String.class,
				Time.class, Time.class,
				MetricViewpoint.class});
			try {
				Metric metric =
					(Metric)am.newArtifact(PrmOnDemandMetric.class,
							description, name, viewpoint, start, end,
							expression);
				this.metricSet.add(metric);
				HashSet<Metric> metrics = this.metricToProjectMap.get(project);
				if ( metrics == null )
					metrics = new HashSet<Metric>();
				metrics.add(metric);
				this.metricToProjectMap.put(project, metrics);
				return metric;
			} catch (UnregisteredArtifactType e) {
				e.printStackTrace();
			}
			// Should not reach here
			return null;
		}

		public Metric newOperationalMetric(Project project,
				Operation operation, MetricExpression expression, String name,
				String description, Time start, Time end,
				MetricViewpoint viewpoint) {
			PrmSecurityManager.roleCheck("newOperationalMetric", getClass(), new Class[] {Project.class,
				Operation.class,
				MetricExpression.class,
				String.class, String.class,
				Time.class, Time.class,
				MetricViewpoint.class});
			try {
				Metric metric =
					(Metric)am.newArtifact(PrmOperationalMetric.class,
							description, name, viewpoint, start, end,
							expression, operation);
				this.metricSet.add(metric);
				HashSet<Metric> metrics = this.metricToProjectMap.get(project);
				if ( metrics == null )
					metrics = new HashSet<Metric>();
				metrics.add(metric);
				this.metricToProjectMap.put(project, metrics);
				System.out.println("Added project and metric pair: " + project.getName() + " and " + metric.getName());
				return metric;
			} catch (UnregisteredArtifactType e) {
				e.printStackTrace();
			}
			// Should not reach here
			return null;
		}

		public void observe(MetricEvent event) {
			PrmSecurityManager.roleCheck("observe", getClass(), MetricEvent.class);
			EventManager em = PRM.getEventManager();
			AttributePair<Class<?>> attribute1 = 
				new AttributePair<Class<?>>("getClass", MetricEvent.class);
			AttributePair<Number> attribute2 =
				new AttributePair<Number>("getMeasure", event.getMeasure());
			em.observe(attribute1, attribute2);
		}

		public void raise(MetricEvent event) {
			PrmSecurityManager.roleCheck("raise", getClass(), MetricEvent.class);
			EventManager em = PRM.getEventManager();
			em.raiseEvent(event);
		}

		public Time getCreationDate() {
			return this.creationDate;
		}

		public String getDescription() {
			return "Default Implementation of MetricsManager";
		}

		public VersionNumber getVersion() {
			return PRM.getPRMVersion();
		}	
	}
}
